/*
 * Copyright 2016-2019 yoara
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package yhao.infra.feature.payment.core.wxpay;

import com.alibaba.fastjson.JSONObject;
import org.apache.http.Consts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import yhao.infra.feature.payment.core.wxpay.config.WinXinConfig;
import yhao.infra.feature.payment.core.wxpay.model.WxBasePostModel;
import yhao.infra.feature.payment.core.wxpay.model.pay.*;
import yhao.infra.feature.payment.core.wxpay.model.query.WxPayQueryModel;
import yhao.infra.feature.payment.core.wxpay.model.refund.*;
import yhao.infra.feature.payment.core.wxpay.util.*;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;
import java.util.Map;

/**
 * ali支付核心处理类
 *
 * @author yoara
 */
public class WeiXinPayService {
    private Logger logger = LoggerFactory.getLogger("paymentLogger");

    private WinXinConfig winXinConfig;
    public WeiXinPayService(WinXinConfig winXinConfig) {
        this.winXinConfig = winXinConfig;
    }

    public WinXinConfig pickConfig(){
        return winXinConfig;
    }
    /**
     * 微信退款查询
     *
     * @param info
     * @return true 已退款   false 未退款
     * @throws Exception
     */
    public boolean wxHasRefundQuery(WxRefundQueryModel info) throws Exception {
        setPostSign(info,winXinConfig);
        HttpsRequest request = new HttpsRequest();
        String result = request.sendPost(winXinConfig.getRefund_query_url(), info);
        Map<String, Object> resp_map = XMLParser.getMapFromXML(result);
        String resp_sign = resp_map.get("sign").toString();
        resp_map.put("sign", "");
        if ("SUCCESS".equals(resp_map.get("return_code").toString())) {
            String local_sign = Signature.getSign(resp_map,winXinConfig);
            if ("SUCCESS".equals(resp_map.get("result_code")) && resp_sign.equals(local_sign)) {
                Integer refundCount = Integer.parseInt(resp_map.get("refund_count").toString());
                return refundCount > 0;
            } else {
                return false;
            }
        }
        throw new RuntimeException();
    }

    /**
     * 微信交易查询
     *
     * @param info
     * @return
     * @throws Exception
     */
    public Map<String, Object> wxHasPayQuery(WxPayQueryModel info) throws Exception {
        setPostSign(info,winXinConfig);
        HttpsRequest request = new HttpsRequest();
        String result = request.sendPost(winXinConfig.getPay_query_url(), info);
        Map<String, Object> resp_map = XMLParser.getMapFromXML(result);
        String resp_sign = resp_map.get("sign").toString();
        resp_map.put("sign", "");
        if ("SUCCESS".equals(resp_map.get("return_code").toString())) {
            String local_sign = Signature.getSign(resp_map,winXinConfig);
            if ("SUCCESS".equals(resp_map.get("result_code")) && resp_sign.equals(local_sign)) {
                //return "SUCCESS".equals(resp_map.get("trade_state"));
                return resp_map;
            } else {
                return null;
            }
        }
        throw new RuntimeException();
    }

    /**
     * 微信退款
     * @throws Exception
     */
    public WxRefundResult wxRefund(WxRefundModel wxRefundModel) throws Exception {
        setPostSign(wxRefundModel,winXinConfig);
        String result = HttpsReqWithCredentials.sslRequestPost(winXinConfig.getRefund_url(), wxRefundModel,winXinConfig);
        WxRefundResData wxRefundResData = (WxRefundResData) Util.getObjectFromXML(result, WxRefundResData.class);

        WxRefundResult refundResult = new WxRefundResult();
        if ("SUCCESS".equals(wxRefundResData.getReturn_code()) && "SUCCESS".equals(wxRefundResData.getResult_code())) {//业务成功
            //验证签名
            String return_sign = wxRefundResData.getSign();
            String sign_local = Signature.getSignFromResponseString(result,winXinConfig);
            if (return_sign != null && return_sign.equals(sign_local)) {//验签成功
                refundResult.setApp_id(wxRefundResData.getAppid());
                refundResult.setOrderId(wxRefundResData.getOut_trade_no());
                refundResult.setRefund_fee(Integer.parseInt(wxRefundResData.getRefund_fee()));
                refundResult.setRefund_id(wxRefundResData.getRefund_id());
                refundResult.setTransaction_id(wxRefundResData.getTransaction_id());
                refundResult.setOut_refund_no(wxRefundResData.getOut_refund_no());
                refundResult.setSignPass(true);
                refundResult.setRefundDone(true);
            } else {
                refundResult.setSignPass(false);
                refundResult.setRefundDone(false);
            }
        } else {//退款失败
            refundResult.setSignPass(false);
            refundResult.setRefundDone(false);
        }
        return refundResult;
    }

    /**
     * 微信wap支付
     *
     * @param model
     * @return
     * @throws Exception
     */
    public JSONObject wxWapPay(WxWapPayModel model, String redirectInfo) throws Exception {
        setPostSign(model,winXinConfig);
        HttpsRequest request = new HttpsRequest();
        String result = request.sendPost(winXinConfig.getUniform_order_url(), model);
        logger.info("[pay][weixin]返回信息:" + result);
        WxWapPayResData resData = (WxWapPayResData) Util.getObjectFromXML(result, WxWapPayResData.class);
        JSONObject json = new JSONObject();
        String sign_local = Signature.getSignFromResponseString(result,winXinConfig);
        if (sign_local.equals(resData.getSign())) {
            if (WxPayResData.SUCCESS.equals(resData.getReturn_code()) && WxPayResData.SUCCESS.equals(resData.getResult_code())) {
                String redirect_url = winXinConfig.getRedirect_url() + "?"+redirectInfo;
                //"?orderNo=" + model.getOut_trade_no() + "&reqSource=" + PaySourceEnums.WECHAT.name();
                json.put("mweb_url", resData.getMweb_url() + "&redirect_url=" + URLEncoder.encode(redirect_url, "utf-8"));
                json.put("isSuccess", true);
            } else {
                json.put("isSuccess", false);
            }
        } else {
            json.put("isSuccess", false);
        }
        return json;
    }

    /**
     * 微信app支付
     * @throws Exception
     */
    public JSONObject wxAppPay(WxAppPayModel payModel) throws Exception {
        setPostSign(payModel,winXinConfig);
        HttpsRequest request = new HttpsRequest();
        String resultStr = request.sendPost(winXinConfig.getUniform_order_url(), payModel);
        logger.info("[pay][weixin]返回信息:" + resultStr);
        WxPayResData resData = (WxPayResData)Util.getObjectFromXML(resultStr, WxPayResData.class);

        JSONObject result = new JSONObject();
        if (resData != null &&
                WxPayResData.SUCCESS.equals(resData.getReturn_code()) &&
                WxPayResData.SUCCESS.equals(resData.getResult_code())) {

            String timeStampStr = String.valueOf(Util.genTimeStamp());
            result.put("appid", winXinConfig.getApp_id());
            result.put("partnerid", winXinConfig.getPartner_id());
            result.put("timestamp", timeStampStr);
            result.put("prepayid", resData.getPrepay_id());
            result.put("noncestr", resData.getNonce_str());
            result.put("package", "Sign=WXPay");
            generateSign(result);
            result.put("isSuccess", true);
            return result;
        }
        result.put("isSuccess", false);
        return result;
    }

    /**
     * 微信预支付，生成二维码
     */
    public JSONObject wxCodePay(WxCodePayModel payModel) throws Exception {
        setPostSign(payModel,winXinConfig);
        HttpsRequest request = new HttpsRequest();
        String resultStr = request.sendPost(winXinConfig.getUniform_order_url(), payModel);
        logger.info("[pay][weixin]返回信息:" + resultStr);
        WxCodePayResData resData = (WxCodePayResData)Util.getObjectFromXML(resultStr, WxCodePayResData.class);

        JSONObject result = new JSONObject();
        if (resData != null &&
                WxPayResData.SUCCESS.equals(resData.getReturn_code()) &&
                WxPayResData.SUCCESS.equals(resData.getResult_code())) {

            result.put("prepayid", resData.getPrepay_id());
            result.put("code_url", resData.getCode_url());
            result.put("isSuccess", true);
            return result;
        }
        result.put("isSuccess", false);
        result.put("err_msg",resData.getErr_code_des());
        return result;
    }

    private JSONObject generateSign(JSONObject prepayIdInfo) {
        StringBuilder builder = new StringBuilder();
        builder.append("appid").append('=').append(winXinConfig.getApp_id()).append('&')
                .append("noncestr").append('=').append(prepayIdInfo.getString("noncestr")).append('&')
                .append("package").append('=').append(prepayIdInfo.getString("package")).append('&')
                .append("partnerid").append('=').append(winXinConfig.getPartner_id()).append('&')
                .append("prepayid").append('=').append(prepayIdInfo.getString("prepayid")).append('&')
                .append("timestamp").append('=').append(prepayIdInfo.getString("timestamp"));
        prepayIdInfo.put("sign", sign(builder));
        return prepayIdInfo;
    }

    private String sign(StringBuilder builder) {
        if (builder.charAt(builder.length() - 1) != '&') {
            builder.append('&');
        }
        builder.append("key=").append(winXinConfig.getPartner_key());
        return MD5.getMessageDigest(builder.toString().getBytes(Consts.UTF_8)).toUpperCase();
    }

    /**
     * 生成微信支付签名
     **/
    private void setPostSign(WxBasePostModel post, WinXinConfig winXinConfig) throws IllegalAccessException {
        post.setAppid(winXinConfig.getApp_id());
        //微信支付分配的商户号
        post.setMch_id(winXinConfig.getPartner_id());

        if(post instanceof WxPayModel){
            ((WxPayModel)post).setNotify_url(
                    winXinConfig.getNotify_url_pre()+((WxPayModel)post).getNotify_business_url());
            if(post instanceof WxWapPayModel){
                ((WxWapPayModel)post).setScene_info("{\"h5_info\":{\"type\":\"Wap\",\"wap_url\":\"" +
                        winXinConfig.getWap_url() + "\",\"wap_name\":\"" + winXinConfig.getWap_name() + "\"}}");
            }
        }
        String sign = Signature.getSign(post,winXinConfig);
        post.setSign(sign);
    }

    //退款回调
    public WxRefundResult refundCallBack(HttpServletRequest request) throws Exception {
        StringBuilder sb = new StringBuilder();
        InputStreamReader isr = new InputStreamReader(request.getInputStream());
        BufferedReader br = new BufferedReader(isr);
        String s;
        while ((s = br.readLine()) != null) {
            sb.append(s);
        }
        String str = sb.toString();
        WxRefundCallBackData data = (WxRefundCallBackData) Util.getObjectFromXML(str, WxRefundCallBackData.class);
        WxRefundResult wxRefundResult = new WxRefundResult();
        if ("SUCCESS".equals(data.getReturn_code())) {
            //解密
            String decryptStr = data.getReq_info();
            byte[] bytes = Base64.getDecoder().decode(decryptStr);
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
            SecretKeySpec key = new SecretKeySpec(MD5.MD5Encode(winXinConfig.getPartner_key()).toLowerCase().getBytes(), "AES");
            cipher.init(Cipher.DECRYPT_MODE, key);
            String decrypt_result = new String(cipher.doFinal(bytes), "UTF-8");
            WxRefundDecryptData wxRefundDecryptInfo = (WxRefundDecryptData) Util.getObjectFromXML(decrypt_result, WxRefundDecryptData.class);
            if ("SUCCESS".equals(wxRefundDecryptInfo.getRefund_status())) {
                wxRefundResult.setSignPass(true);
                wxRefundResult.setRefundDone(true);
                wxRefundResult.setApp_id(data.getAppid());
                wxRefundResult.setOut_refund_no(wxRefundDecryptInfo.getOut_refund_no());
                wxRefundResult.setTransaction_id(wxRefundDecryptInfo.getTransaction_id());
                wxRefundResult.setOrderId(wxRefundDecryptInfo.getOut_trade_no());
                wxRefundResult.setRefund_id(wxRefundDecryptInfo.getRefund_id());
                wxRefundResult.setRefund_fee(Integer.parseInt(wxRefundDecryptInfo.getRefund_fee()));
                return wxRefundResult;

            } else {
                wxRefundResult.setRefundDone(false);
            }
        }
        wxRefundResult.setRefundDone(false);
        return wxRefundResult;
    }

    /**
     * 支付回调
     * @throws Exception
     */
    public WxPayCallBack payCallBack(HttpServletRequest request) throws Exception {
        StringBuilder sb = new StringBuilder();
        InputStreamReader isr = new InputStreamReader(request.getInputStream());
        BufferedReader br = new BufferedReader(isr);
        String s;
        while ((s = br.readLine()) != null) {
            sb.append(s);
        }
        String str = sb.toString();
        WxPayCallBackData data = (WxPayCallBackData) Util.getObjectFromXML(str, WxPayCallBackData.class);
        WxPayCallBack result = new WxPayCallBack();
        boolean isPaySuccess = "SUCCESS".equals(data.getReturn_code()) && "SUCCESS".equals(data.getResult_code());

        if (isPaySuccess) {
            result.setApp_id(data.getAppid());
            result.setAmount(new Integer(data.getTotal_fee()));
            result.setBackStr(str);
            result.setOrderId(data.getOut_trade_no());
            result.setPayTradeNo(data.getTransaction_id());
            result.setPayDone(true);
            //验证签名
            String sign = data.getSign();
            String sign_local = Signature.getSignFromResponseString(str,winXinConfig);
            result.setSignPass(sign != null && sign.equals(sign_local));
            Date date = new SimpleDateFormat("yyyyMMddHHmmss").parse(data.getTime_end());
            result.setEndTime(date);
        } else {
            result.setPayDone(false);
            result.setSignPass(false);
        }
        return result;
    }
}
